﻿using FollowMe.GCM;
using FollowMe.Infrastructure.UnitOfWork;
using FollowMe.Model;
using FollowMe.Model.Messages;
using FollowMe.Services.Mappers;
using FollowMe.Services.Request;
using FollowMe.Services.Response;
using FollowMe.Services.View;
using System;
using System.Collections.Generic;
using System.Linq;

namespace FollowMe.Services
{
    /// <summary>
    /// Service layer that uses DAL layers, coordinates CRUD operations
    /// </summary>
    public class FollowMeService : IFollowMeService
    {
        /// <summary>
        /// The Unit of Work
        /// </summary>
        private IUnitOfWork _uow;
        //private ILocationRepository _locationRepository;
        /// <summary>
        /// The Android registrationId repository
        /// </summary>
        private IRegistrationIDRepository _registrationIDRepository;
        /// <summary>
        /// The member repository
        /// </summary>
        private IMemberRepository _memberRepository;
        /// <summary>
        /// The friend repository
        /// </summary>
        private IFriendRepository _friendRepository;
        /// <summary>
        /// The message repository
        /// </summary>
        private IMessageRepository _messageRepository;
        /// <summary>
        /// The server key for Google Push Notifications
        /// </summary>
        private readonly static String SERVER_KEY = "AIzaSyDThgLlQ-k0Y7KrxDrR0r0TSqoOxuVpLQ8";

        /// <summary>
        /// Initializes a new instance of the <see cref="FollowMeService"/> class.
        /// </summary>
        /// <param name="registrationIDRepository">The registrationid repository.</param>
        /// <param name="memberRepository">The member repository.</param>
        /// <param name="friendRepo">The friend repo.</param>
        /// <param name="messageRepo">The message repo.</param>
        /// <param name="unitOfWork">The unit of work.</param>
        public FollowMeService(//ILocationRepository locationRepository,
            IRegistrationIDRepository registrationIDRepository, 
                             IMemberRepository memberRepository,
                             IFriendRepository friendRepo,
                             IMessageRepository messageRepo,
                             IUnitOfWork unitOfWork)
        {
            _uow = unitOfWork;
            _memberRepository = memberRepository;
            _friendRepository = friendRepo;
            _messageRepository = messageRepo;
            //_locationRepository = locationRepository;
            _registrationIDRepository = registrationIDRepository;
        }

        /// <summary>
        /// Adds a new member.
        /// </summary>
        /// <param name="_member">The member registration request.</param>
        /// <returns>The member as a MemberView</returns>
        public MemberView addNewMember(RegisterMemberRequest _member)
        {
            Member member = NewMemberFactory.createNewMember(_member.memberid, _member.Email, _member.Email, _member.firstname, _member.lastname);

            _memberRepository.Add(member);
            _uow.Commit();
            return member.ConvertToMemberView();
        }

        /// <summary>
        /// Updates the member.
        /// </summary>
        /// <param name="_member">The memberid in a request.</param>
        /// <exception cref="System.NotImplementedException"></exception>
        public void updateMember(UpdateMemberDetailsRequest _member)
        {
            throw new NotImplementedException();
            //!!!!!!!!!!!!!!!!TODOTODO!!!!!!!!!!!!!!!!!! this is bad
            //Member member = _member.ConvertToMember();
            //_memberRepository.Save(member);
            //_uow.Commit();
        }

        /// <summary>
        /// Sends a message to a user
        // Also send a push notification after commit
        /// </summary>
        /// <param name="_message">The message to send.  Includes memberIDFrom, To, and the message</param>
        /// <returns></returns>
        public MessageView sendMessageTo(SendMessageRequest _message)
        {
            if (_message == null) { return null; }

            List<Friend> friends = _friendRepository.FindIAddedBy(_message.memberid).Where(m => m.FriendAddedID == _message.memberToID).ToList();
            friends.AddRange(_friendRepository.FindAddedMeBy(_message.memberid).Where(m => m.MemberAddingID == _message.memberToID).ToList());

            //onlt send if they are friends
            if (friends.Count != 0 && (_message.memberid != _message.memberToID))
            {
                Member memberFrom = _memberRepository.FindBy(_message.memberid);
                Member memberTo = _memberRepository.FindBy(_message.memberToID);
                if (memberFrom != null && memberTo != null)
                {
                   
                    //create new text message based on the request
                    AMessage newMess = new TextMessage
                    {
                        MessageSenderId = _message.memberid,
                        MessageReceiverId = _message.memberToID,
                        Content = _message.content
                    };

                    //Get factory to persist message
                    Message theMessage = Message.getFactory().CreateNew(newMess);

                    //add, commit
                    _messageRepository.Add(theMessage);
                    _uow.Commit();
                    

                    //get registration ids of the user the message was sent to
                    IEnumerable<RegistrationID> regIds = _registrationIDRepository.FindByMember(memberTo.MemberId);
                    List<String> ids = new List<string>();

                    foreach (RegistrationID regid in regIds)
                    {
                        ids.Add(regid.RegID);
                    }

                    //if they have ids, send them a notification
                    if (ids.Count() > 0)
                    {
                        GCMMessageView gcmMessage = new GCMMessageView
                        {
                            memberFromID = theMessage.MessageSenderId,
                            memberToID = theMessage.MessageReceiverId,
                            content = theMessage.Content,
                            date = theMessage.Date,
                            messageID = theMessage.MessageId,
                            read = false,
                            messageType = theMessage.MessageType
                        };

                        gcmMessage.statustext = "New message from: " + memberFrom.FirstName + " " + memberFrom.LastName;

                        GCMMessage<GCMMessageView> gcm = new GCMMessage<GCMMessageView>
                        {
                            registration_id = ids[0],
                            registration_ids = ids.ToArray(),
                            data = gcmMessage

                        };

                        GCMResponse gcmRes = PushNotification<GCMMessageView>.POST(gcm, SERVER_KEY);

                        if (!gcmRes.success)
                        {
                            //do something about it
                        }
                    }
                    return (theMessage.ConvertToMessageView());
                }
            }
            return null;
        }

        /// <summary>
        /// Finds the sent messages from a user
        /// </summary>
        /// <param name="_member">The memberid in a request.</param>
        /// <returns>The MessageViews</returns>
        public FindMessagesResponse findSentMessagesFrom(FindMemberInfoRequest _member)
        {
            if (_member == null) { return null; }

            //simply create a response that includes the messages
            FindMessagesResponse response = new FindMessagesResponse
            {
                success = true,
                statustext = "Ok",
                messages = _messageRepository.FindISent(_member.memberid).ConvertToMessageViews()
            };

            return response;
        }

        /// <summary>
        /// Finds the received messages for a user.
        /// </summary>
        /// <param name="_member">The memberid as a request.</param>
        /// <returns>The MessageViews</returns>
        public FindMessagesResponse findReceivedMessagesFor(FindMemberInfoRequest _member)
        {
            if (_member == null) { return null; }

            //simply create a response that includes the messages
            FindMessagesResponse response = new FindMessagesResponse
            {
                success = true,
                statustext = "Ok",
                messages = _messageRepository.FindIReceived(_member.memberid).ConvertToMessageViews()
            };
            return response;
        }

        /// <summary>
        /// Finds the unread messages for a user.
        /// </summary>
        /// <param name="_member">The memberis as a request.</param>
        /// <returns>The MessageViews</returns>
        public FindMessagesResponse findUnreadMessagesFor(FindMemberInfoRequest _member)
        {
            if (_member == null) { return null; }
            FindMessagesResponse response = new FindMessagesResponse
            {
                success = true,
                statustext = "Ok",
                messages = _messageRepository.FindIReceived(_member.memberid).Where(m => m.Read == false).ConvertToMessageViews()
            };
            return response;
        }

        /// <summary>
        /// Marks a message as read.
        /// </summary>
        /// <param name="request">The messageID as a request.</param>
        /// <returns>The results of the action.</returns>
        public HTTPResults markAsRead(MessageRequest request)
        {
            HTTPResults results = new HTTPResults { };

            Message message = _messageRepository.FindBy(request.messageId);
            if (message == null)
            {
                return new HTTPResults
                {
                    statustext = "Message not found!",
                    success = false
                };
            }

            message.markAsRead();
            _messageRepository.Save(message);
            _uow.Commit();

            return new HTTPResults
                {
                    statustext = "Ok",
                    success = true
                };
        }

        /// <summary>
        /// Adds a new location.
        /// </summary>
        /// <param name="_location">The _location.</param>
        /// <exception cref="System.NotImplementedException"></exception>
        public void addLocation(AddLocationRequest _location)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Gets the locations for a user
        /// </summary>
        /// <param name="_member">The _member.</param>
        /// <returns>A list of locations</returns>
        /// <exception cref="System.NotImplementedException"></exception>
        public IEnumerable<LocationView> getLocationsFor(FindMemberInfoRequest _member)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Adds a new friend.
        // Also sends a push notification to the user being added.
        /// </summary>
        /// <param name="request">The request: memberid from and the id of the user to add.</param>
        /// <returns>The friend as a FriendView</returns>
        public FriendView addFriend(AddFriendRequest request)
        {

            Member memberfrom = _memberRepository.FindBy(request.memberid);
            Member newFriend = _memberRepository.FindBy(request.NewFriend);

            if (memberfrom == null || newFriend == null)
            {
                return null;
            }

            List<Friend> friends = _friendRepository.FindIAddedBy(request.memberid).Where(m => m.FriendAddedID == request.NewFriend).ToList();
            friends.AddRange(_friendRepository.FindAddedMeBy(request.memberid).Where(m => m.MemberAddingID == request.NewFriend).ToList());

           
            Friend friend;
            if (null == friends || friends.Count == 0
                    && !memberfrom.isThisMember(newFriend))
            {
                friend = FriendFactory.CreateFriendship(memberfrom, newFriend);
                _friendRepository.Add(friend);
                _uow.Commit();
            }
            else
            {
                return null;
            }

            FriendView friendres = newFriend.ConvertToFriendView();

           //get registration ids of the newfriend and try to notify them.
            IEnumerable<RegistrationID> regIds = _registrationIDRepository.FindByMember(newFriend.MemberId);
            List<String> ids = new List<string>();

            foreach (RegistrationID regid in regIds)
            {
                ids.Add(regid.RegID);
            }

            //if they have some, send them a notification including the friendview of the member that added them.
            if (ids.Count() > 0)
            {
                GCMFriendView gcmMessage = new GCMFriendView
                {
                    email = memberfrom.Email,
                    middleInitial = memberfrom.MiddleInitial,
                    firstName = memberfrom.FirstName,
                    lastName = memberfrom.LastName,
                    friendID = memberfrom.MemberId,
                    userName = memberfrom.UserName,
                };

                gcmMessage.statustext = memberfrom.FirstName + " " + memberfrom.LastName + " added you!";

                GCMMessage<GCMFriendView> gcm = new GCMMessage<GCMFriendView>
                {
                    registration_id = ids[0],
                    registration_ids = ids.ToArray(),
                    data = gcmMessage
                };

                GCMResponse gcmRes = PushNotification<GCMFriendView>.POST(gcm, SERVER_KEY);

                if (!gcmRes.success)
                {
                    //do something about it
                }
            }
            return friendres;
        }

        /// <summary>
        /// Deletes a friend.
        /// </summary>
        /// <param name="request">The request: member performing action, friend to delete.</param>
        /// <returns></returns>
        public HTTPResults deleteFriend(DeleteFriendRequest request)
        {
            List<Friend> friends = _friendRepository.FindIAddedBy(request.memberid).Where(m => m.FriendAddedID == request.ExFriend).ToList();
            friends.AddRange(_friendRepository.FindAddedMeBy(request.memberid).Where(m => m.MemberAddingID == request.ExFriend).ToList());

            if (friends.Count() < 1)
            {
                return new HTTPResults
                {
                    success = false,
                    statustext = "Friend not found!"
                };
            }

            _friendRepository.Remove(friends.First());
            _uow.Commit();

            return new HTTPResults
                {
                    success = true,
                    statustext = "Friend removed."
                };
        }

        /// <summary>
        /// Finds all of a members information.
        /// </summary>
        /// <param name="member">The member.</param>
        /// <returns>All user information including messsages, friends (as views)</returns>
        public MemberView findMemberInfo(FindMemberInfoRequest member)
        {
            Member thisMember = _memberRepository.FindBy(member.memberid);
            List<FriendView> friends = new List<FriendView>();
            List<MessageView> messages = new List<MessageView>();

            messages.AddRange(_messageRepository.FindISent(member.memberid).ConvertToMessageViews());
            messages.AddRange(_messageRepository.FindIReceived(member.memberid).ConvertToMessageViews());

            foreach (Friend friend in _friendRepository.FindAddedMeBy(member.memberid))
            {
                FriendView temp = new FriendView { friendID = friend.MemberAddingID };
                friends.Add(temp);
            }

            foreach (Friend friend in _friendRepository.FindIAddedBy(member.memberid))
            {
                FriendView temp = new FriendView { friendID = friend.FriendAddedID };
                friends.Add(temp);
            }

            MemberView mem = thisMember.ConvertToMemberView();
            mem.friends = friends;
            mem.messages = messages;

            foreach (FriendView friend in mem.friends)
            {
                FriendView friendView = _memberRepository.FindBy(friend.friendID).ConvertToFriendView();
                friend.email = friendView.email;
                friend.firstName = friendView.firstName;
                friend.lastName = friendView.lastName;
                friend.middleInitial = friendView.middleInitial;
                friend.userName = friendView.userName;
            }
            return mem;
        }

        /// <summary>
        /// Searches for a new friend by username.
        /// </summary>
        /// <param name="request">The request: a search string.</param>
        /// <returns>A list of FriendViews</returns>
        public FindFriendResponse searchForFriendsByUsername(FindFriendRequest request)
        {
            Member memberfrom = _memberRepository.FindBy(request.memberid);
            List<Member> friendsSearch = _memberRepository.FindBy(request.userName).ToList();
            List<Member> results = new List<Member>();
            List<Friend> friends = _friendRepository.FindIAddedBy(request.memberid).ToList();
            friends.AddRange(_friendRepository.FindAddedMeBy(request.memberid));

            foreach (Member friend in friendsSearch)
            {
                if ((null == friends.FirstOrDefault(p => p.MemberAddingID == friend.MemberId || p.FriendAddedID == friend.MemberId))
                    && !memberfrom.isThisMember(friend))
                {
                    results.Add(friend);
                }
            };


            FindFriendResponse friendsFound = new FindFriendResponse
            {
                friends = results.ConvertToFriendViews(),
                success = true
            };

            return friendsFound;
        }


        /// <summary>
        /// Adds an Android Registration ID for a user (who can have multiple devices)
        /// </summary>
        /// <param name="request">The request: a memberid, device id, GCM registration ID.</param>
        /// <returns>The results of the action</returns>
        public HTTPResults addRegistrationID(AddRegistrationIDRequest request)
        {
            Member member = _memberRepository.FindBy(request.memberid);
            if (member == null)
            {
                return new HTTPResults
                {
                   success = false,
                   statustext = "Member not found"
                };
            }

            List<RegistrationID> ids = _registrationIDRepository.FindByMember(request.memberid).ToList();
            

            if (ids.Count() == 0)
            {
                _registrationIDRepository.Add(new RegistrationID(request.memberid, request.registrationId, request.deviceID));
            }
            else
            {
                if(ids.Where(m=>m.AndroidID == request.deviceID).Count() > 0){
                    _registrationIDRepository.RemoveAll(ids.Where(m => m.AndroidID == request.deviceID));
                    _registrationIDRepository.Add(new RegistrationID(request.memberid, request.registrationId, request.deviceID));
                }
            }

            _uow.Commit();
            return new HTTPResults
            {
                statustext = "Ok",
                success = true
            };
        }
    }
}